﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using Cinchoo.Core;
using System.IO;
using System.Data;
using Cinchoo.Core.ETL;

namespace ChoWorkflowServer.Model
{
    public enum ChoTokenEnum
    {
        [Description(@"[*<>\?\-+/A-Za-z->!_0-9]+")]
        SYMBOL,
        [Description(@"EXITCODE")]
        EXITCODE,
        [Description(@"SUCCESS")]
        SUCCESS,
        [Description(@"FAILED")]
        FAILED,
        [Description(@"ABORTED")]
        ABORTED,
        [Description(@"STOPPPED")]
        STOPPPED,
        [Description(@"TIMEDOUT")]
        TIMEDOUT,
        [Description(@"NOTRUNNING")]
        NOTRUNNING,
        [Description(@"&&")]
        AND,
        [Description(@"||")]
        OR,
        [Description(@"TRUE")]
        TRUE,
        [Description(@"FALSE")]
        FALSE,
        [Description(@"!")]
        NOT,
        [Description(@"=")]
        EQUAL,
        [Description(@"!=")]
        NOTEQUAL,
        [Description(@"<")]
        LESSTHAN,
        [Description(@"<=")]
        LESSTHANEQUAL,
        [Description(@">")]
        GREATERTHAN,
        [Description(@">=")]
        GREATERTHANEQUAL,
        [Description(@"\(")]
        LEFT,
        [Description(@"\)")]
        RIGHT,
        [Description(@"\s")]
        SPACE,
    }

    public class ChoLexParser : IDisposable
    {
        private readonly IChoDataModel _dataModel = ChoDataModelFactory.New();

        private readonly ChoTokenDefinition<ChoTokenEnum>[] _tokens = new ChoTokenDefinition<ChoTokenEnum>[]
            {
                new ChoTokenDefinition<ChoTokenEnum>(ChoEnum.ToDescription(ChoTokenEnum.EXITCODE), ChoTokenEnum.EXITCODE),

                new ChoTokenDefinition<ChoTokenEnum>(ChoEnum.ToDescription(ChoTokenEnum.SUCCESS), ChoTokenEnum.SUCCESS),
                new ChoTokenDefinition<ChoTokenEnum>(ChoEnum.ToDescription(ChoTokenEnum.FAILED), ChoTokenEnum.FAILED),
                new ChoTokenDefinition<ChoTokenEnum>(ChoEnum.ToDescription(ChoTokenEnum.ABORTED), ChoTokenEnum.ABORTED),
                new ChoTokenDefinition<ChoTokenEnum>(ChoEnum.ToDescription(ChoTokenEnum.STOPPPED), ChoTokenEnum.STOPPPED),
                new ChoTokenDefinition<ChoTokenEnum>(ChoEnum.ToDescription(ChoTokenEnum.TIMEDOUT), ChoTokenEnum.TIMEDOUT),
                new ChoTokenDefinition<ChoTokenEnum>(ChoEnum.ToDescription(ChoTokenEnum.NOTRUNNING), ChoTokenEnum.NOTRUNNING),
                
                new ChoTokenDefinition<ChoTokenEnum>(ChoEnum.ToDescription(ChoTokenEnum.EQUAL), ChoTokenEnum.EQUAL),
                new ChoTokenDefinition<ChoTokenEnum>(ChoEnum.ToDescription(ChoTokenEnum.NOTEQUAL), ChoTokenEnum.NOTEQUAL),
                new ChoTokenDefinition<ChoTokenEnum>(ChoEnum.ToDescription(ChoTokenEnum.LESSTHAN), ChoTokenEnum.LESSTHAN),
                new ChoTokenDefinition<ChoTokenEnum>(ChoEnum.ToDescription(ChoTokenEnum.LESSTHANEQUAL), ChoTokenEnum.LESSTHANEQUAL),
                new ChoTokenDefinition<ChoTokenEnum>(ChoEnum.ToDescription(ChoTokenEnum.GREATERTHAN), ChoTokenEnum.GREATERTHAN),
                new ChoTokenDefinition<ChoTokenEnum>(ChoEnum.ToDescription(ChoTokenEnum.GREATERTHANEQUAL), ChoTokenEnum.GREATERTHANEQUAL),

                new ChoTokenDefinition<ChoTokenEnum>(ChoEnum.ToDescription(ChoTokenEnum.AND), ChoTokenEnum.AND),
                new ChoTokenDefinition<ChoTokenEnum>(ChoEnum.ToDescription(ChoTokenEnum.OR), ChoTokenEnum.OR),
                new ChoTokenDefinition<ChoTokenEnum>(ChoEnum.ToDescription(ChoTokenEnum.NOT), ChoTokenEnum.NOT),

                new ChoTokenDefinition<ChoTokenEnum>(ChoEnum.ToDescription(ChoTokenEnum.LEFT), ChoTokenEnum.LEFT),
                new ChoTokenDefinition<ChoTokenEnum>(ChoEnum.ToDescription(ChoTokenEnum.RIGHT), ChoTokenEnum.RIGHT),

                new ChoTokenDefinition<ChoTokenEnum>(ChoEnum.ToDescription(ChoTokenEnum.SPACE), ChoTokenEnum.SPACE),

                new ChoTokenDefinition<ChoTokenEnum>(ChoEnum.ToDescription(ChoTokenEnum.TRUE), ChoTokenEnum.TRUE),
                new ChoTokenDefinition<ChoTokenEnum>(ChoEnum.ToDescription(ChoTokenEnum.FALSE), ChoTokenEnum.FALSE),

                new ChoTokenDefinition<ChoTokenEnum>(ChoEnum.ToDescription(ChoTokenEnum.SYMBOL), ChoTokenEnum.SYMBOL),
            };
        private readonly ChoLexer<ChoTokenEnum> _lex = null;
        private readonly Stack<ChoTokenContent<ChoTokenEnum>> stack = new Stack<ChoTokenContent<ChoTokenEnum>>();

        public ChoLexParser(string text)
        {
            ChoGuard.ArgumentNotNullOrEmpty(text, "Text");

            _lex = new ChoLexer<ChoTokenEnum>(new StringReader(text), _tokens);
        }

        public bool Evaluate()
        {
            while (true)
            {
                ChoTokenContent<ChoTokenEnum> tokenContent = _lex.Peek();
                if (tokenContent == null) break;

                if (tokenContent.Token == ChoTokenEnum.RIGHT)
                {
                    _lex.Next();
                    EvaluateWhile(stack);
                }
                else if (tokenContent.Token == ChoTokenEnum.SPACE)
                {
                    _lex.Next();
                }
                else
                {
                    stack.Push(_lex.Next());
                }

                //Console.WriteLine("Token: {0} Contents: {1}", o.Token, o.TokenContent);

            }
            while (stack.Count > 2)
                EvaluateWhile(stack, true);

            if (stack.Count != 1)
                throw new InvalidExpressionException();
            ChoTokenContent<ChoTokenEnum> tokenContent1 = stack.Pop();
            if (tokenContent1.Token == ChoTokenEnum.FALSE)
                return false;
            else if (tokenContent1.Token == ChoTokenEnum.TRUE)
                return true;
            else
                throw new InvalidExpressionException();
        }

        private void EvaluateWhile(Stack<ChoTokenContent<ChoTokenEnum>> stack, bool end = false)
        {
            while (stack.Count > 0)
            {
                ChoTokenContent<ChoTokenEnum> tokenContent = stack.Pop();
                if (tokenContent == null) continue;

                if (tokenContent.Token == ChoTokenEnum.LEFT)
                    return;
                else if (tokenContent.Token == ChoTokenEnum.SYMBOL)
                {
                    if (stack.Count == 0)
                        throw new InvalidOperationException("Incomplete expression.");

                    ChoTokenContent<ChoTokenEnum> tokenContent1 = stack.Pop();
                    if (tokenContent1.Token == ChoTokenEnum.LEFT)
                    {
                        if (stack.Count == 0)
                            throw new InvalidOperationException("Incomplete expression.");

                        tokenContent1 = stack.Pop();

                        if (tokenContent1.Token == ChoTokenEnum.ABORTED
                            || tokenContent1.Token == ChoTokenEnum.SUCCESS
                            || tokenContent1.Token == ChoTokenEnum.FAILED)
                        {
                            stack.Push(EvaluateJobStatus(tokenContent1.Token, tokenContent.TokenContent));
                            return;
                        }
                        else if (tokenContent1.Token == ChoTokenEnum.EXITCODE)
                        {
                            stack.Push(EvaluateExitCode(tokenContent1.Token, tokenContent.TokenContent));
                            return;
                        }
                    }
                    else if (tokenContent1.Token == ChoTokenEnum.EQUAL
                            || tokenContent1.Token == ChoTokenEnum.NOTEQUAL
                            || tokenContent1.Token == ChoTokenEnum.LESSTHAN
                            || tokenContent1.Token == ChoTokenEnum.LESSTHANEQUAL
                            || tokenContent1.Token == ChoTokenEnum.GREATERTHAN
                            || tokenContent1.Token == ChoTokenEnum.GREATERTHANEQUAL
                        )
                    {
                        if (stack.Count == 0)
                            throw new InvalidOperationException("Incomplete expression.");

                        ChoTokenContent<ChoTokenEnum> tokenContent2 = stack.Pop();

                        if (tokenContent2.Token == ChoTokenEnum.SYMBOL)
                        {
                            stack.Push(EvaluateExitCodeExpr(tokenContent1.Token, Convert.ToInt32(tokenContent2.TokenContent), Convert.ToInt32(tokenContent.TokenContent)));
                            return;
                        }
                        else
                            throw new InvalidOperationException("Incomplete expression.");
                    }
                    else
                        throw new InvalidExpressionException("Incomplete expression.");
                }
                else if (tokenContent.Token == ChoTokenEnum.TRUE
                    || tokenContent.Token == ChoTokenEnum.FALSE)
                {
                    if (stack.Count == 0)
                        throw new InvalidOperationException("Incomplete expression.");

                    ChoTokenContent<ChoTokenEnum> tokenContent1 = stack.Pop();
                    if (tokenContent1.Token == ChoTokenEnum.OR)
                    {
                        if (stack.Count == 0)
                            throw new InvalidOperationException("Incomplete expression.");

                        tokenContent1 = stack.Pop();

                        if (!end)
                        {
                            ChoTokenContent<ChoTokenEnum> tokenContent2 = stack.Pop();
                            if (tokenContent2.Token != ChoTokenEnum.LEFT)
                                throw new InvalidExpressionException("Incomplete expression.");
                        }

                        if (tokenContent.Token == ChoTokenEnum.FALSE
                            && tokenContent1.Token == ChoTokenEnum.FALSE)
                            stack.Push(new ChoTokenContent<ChoTokenEnum>(ChoTokenEnum.FALSE, ChoEnum.ToDescription(ChoTokenEnum.FALSE)));
                        else
                            stack.Push(new ChoTokenContent<ChoTokenEnum>(ChoTokenEnum.TRUE, ChoEnum.ToDescription(ChoTokenEnum.TRUE)));

                        return;
                    }
                    else if (tokenContent1.Token == ChoTokenEnum.AND)
                    {
                        if (stack.Count == 0)
                            throw new InvalidOperationException("Incomplete expression.");

                        tokenContent1 = stack.Pop();

                        if (!end)
                        {
                            ChoTokenContent<ChoTokenEnum> tokenContent2 = stack.Pop();
                            if (tokenContent2.Token != ChoTokenEnum.LEFT)
                                throw new InvalidExpressionException("Incomplete expression.");
                        }

                        if (tokenContent.Token == tokenContent1.Token)
                            stack.Push(new ChoTokenContent<ChoTokenEnum>(ChoTokenEnum.TRUE, ChoEnum.ToDescription(ChoTokenEnum.TRUE)));
                        else
                            stack.Push(new ChoTokenContent<ChoTokenEnum>(ChoTokenEnum.FALSE, ChoEnum.ToDescription(ChoTokenEnum.FALSE)));

                        return;
                    }
                    else if (tokenContent1.Token == ChoTokenEnum.NOT)
                    {
                        if (tokenContent.Token == ChoTokenEnum.TRUE)
                            stack.Push(new ChoTokenContent<ChoTokenEnum>(ChoTokenEnum.FALSE, ChoEnum.ToDescription(ChoTokenEnum.FALSE)));
                        else if (tokenContent.Token == ChoTokenEnum.FALSE)
                            stack.Push(new ChoTokenContent<ChoTokenEnum>(ChoTokenEnum.TRUE, ChoEnum.ToDescription(ChoTokenEnum.TRUE)));
                        else
                            throw new InvalidExpressionException("Incomplete expression.");

                        return;
                    }
                    else
                        throw new InvalidExpressionException("Incomplete expression.");
                }
                else
                    throw new InvalidExpressionException("Incomplete expression.");
            }
        }

        protected virtual ChoTokenContent<ChoTokenEnum> EvaluateJobStatus(ChoTokenEnum token, string jobName)
        {
            Tuple<string, string> taskSchedulePair = ChoJobNameHelper.FromJobName(jobName);
            int taskId = _dataModel.GetTaskId(taskSchedulePair.Item1);
            if (taskId <= 0)
            {
                throw new InvalidOperationException("'{0}' task not exists.".FormatString(jobName));
            }

            ChoETLWorkflowStatus taskStatus = _dataModel.GetTaskStatus(taskSchedulePair.Item1, taskSchedulePair.Item2);
            if (token == ChoTokenEnum.ABORTED)
            {
                if (taskStatus == ChoETLWorkflowStatus.ABORTED)
                    return new ChoTokenContent<ChoTokenEnum>(ChoTokenEnum.TRUE, ChoEnum.ToDescription(ChoTokenEnum.TRUE));
                else
                    return new ChoTokenContent<ChoTokenEnum>(ChoTokenEnum.FALSE, ChoEnum.ToDescription(ChoTokenEnum.FALSE));
            }
            else if (token == ChoTokenEnum.FAILED)
            {
                if (taskStatus == ChoETLWorkflowStatus.FAILED)
                    return new ChoTokenContent<ChoTokenEnum>(ChoTokenEnum.TRUE, ChoEnum.ToDescription(ChoTokenEnum.TRUE));
                else
                    return new ChoTokenContent<ChoTokenEnum>(ChoTokenEnum.FALSE, ChoEnum.ToDescription(ChoTokenEnum.FALSE));
            }
            else if (token == ChoTokenEnum.SUCCESS)
            {
                if (taskStatus == ChoETLWorkflowStatus.SUCCESS)
                    return new ChoTokenContent<ChoTokenEnum>(ChoTokenEnum.TRUE, ChoEnum.ToDescription(ChoTokenEnum.TRUE));
                else
                    return new ChoTokenContent<ChoTokenEnum>(ChoTokenEnum.FALSE, ChoEnum.ToDescription(ChoTokenEnum.FALSE));
            }
            else if (token == ChoTokenEnum.STOPPPED)
            {
                if (taskStatus == ChoETLWorkflowStatus.STOPPED)
                    return new ChoTokenContent<ChoTokenEnum>(ChoTokenEnum.TRUE, ChoEnum.ToDescription(ChoTokenEnum.TRUE));
                else
                    return new ChoTokenContent<ChoTokenEnum>(ChoTokenEnum.FALSE, ChoEnum.ToDescription(ChoTokenEnum.FALSE));
            }
            else if (token == ChoTokenEnum.TIMEDOUT)
            {
                if (taskStatus == ChoETLWorkflowStatus.TIMEDOUT)
                    return new ChoTokenContent<ChoTokenEnum>(ChoTokenEnum.TRUE, ChoEnum.ToDescription(ChoTokenEnum.TRUE));
                else
                    return new ChoTokenContent<ChoTokenEnum>(ChoTokenEnum.FALSE, ChoEnum.ToDescription(ChoTokenEnum.FALSE));
            }
            else if (token == ChoTokenEnum.NOTRUNNING)
            {
                if (taskStatus != ChoETLWorkflowStatus.RUNNING)
                    return new ChoTokenContent<ChoTokenEnum>(ChoTokenEnum.TRUE, ChoEnum.ToDescription(ChoTokenEnum.TRUE));
                else
                    return new ChoTokenContent<ChoTokenEnum>(ChoTokenEnum.FALSE, ChoEnum.ToDescription(ChoTokenEnum.FALSE));
            }
            else
                return new ChoTokenContent<ChoTokenEnum>(ChoTokenEnum.FALSE, ChoEnum.ToDescription(ChoTokenEnum.FALSE));
        }

        protected virtual ChoTokenContent<ChoTokenEnum> EvaluateExitCode(ChoTokenEnum token, string jobName)
        {
            Tuple<string, string> taskSchedulePair = ChoJobNameHelper.FromJobName(jobName);
            int taskId = _dataModel.GetTaskId(taskSchedulePair.Item1);
            if (taskId <= 0)
            {
                throw new InvalidOperationException("'{0}' task not exists.".FormatString(jobName));
            }

            int lExitCode = _dataModel.GetTaskExitCode(taskSchedulePair.Item1, taskSchedulePair.Item2);
            return new ChoTokenContent<ChoTokenEnum>(ChoTokenEnum.SYMBOL, lExitCode.ToString());
        }

        protected virtual ChoTokenContent<ChoTokenEnum> EvaluateExitCodeExpr(ChoTokenEnum opToken, int lExitCode, int rExitCode)
        {
            if (opToken == ChoTokenEnum.EQUAL)
            {
                if (lExitCode == rExitCode)
                    return new ChoTokenContent<ChoTokenEnum>(ChoTokenEnum.TRUE, ChoEnum.ToDescription(ChoTokenEnum.TRUE));
                else
                    return new ChoTokenContent<ChoTokenEnum>(ChoTokenEnum.FALSE, ChoEnum.ToDescription(ChoTokenEnum.FALSE));
            }
            else if (opToken == ChoTokenEnum.NOTEQUAL)
            {
                if (lExitCode != rExitCode)
                    return new ChoTokenContent<ChoTokenEnum>(ChoTokenEnum.TRUE, ChoEnum.ToDescription(ChoTokenEnum.TRUE));
                else
                    return new ChoTokenContent<ChoTokenEnum>(ChoTokenEnum.FALSE, ChoEnum.ToDescription(ChoTokenEnum.FALSE));
            }
            else if (opToken == ChoTokenEnum.LESSTHAN)
            {
                if (lExitCode < rExitCode)
                    return new ChoTokenContent<ChoTokenEnum>(ChoTokenEnum.TRUE, ChoEnum.ToDescription(ChoTokenEnum.TRUE));
                else
                    return new ChoTokenContent<ChoTokenEnum>(ChoTokenEnum.FALSE, ChoEnum.ToDescription(ChoTokenEnum.FALSE));
            }
            else if (opToken == ChoTokenEnum.LESSTHANEQUAL)
            {
                if (lExitCode <= rExitCode)
                    return new ChoTokenContent<ChoTokenEnum>(ChoTokenEnum.TRUE, ChoEnum.ToDescription(ChoTokenEnum.TRUE));
                else
                    return new ChoTokenContent<ChoTokenEnum>(ChoTokenEnum.FALSE, ChoEnum.ToDescription(ChoTokenEnum.FALSE));
            }
            else if (opToken == ChoTokenEnum.GREATERTHAN)
            {
                if (lExitCode > rExitCode)
                    return new ChoTokenContent<ChoTokenEnum>(ChoTokenEnum.TRUE, ChoEnum.ToDescription(ChoTokenEnum.TRUE));
                else
                    return new ChoTokenContent<ChoTokenEnum>(ChoTokenEnum.FALSE, ChoEnum.ToDescription(ChoTokenEnum.FALSE));
            }
            else if (opToken == ChoTokenEnum.GREATERTHANEQUAL)
            {
                if (lExitCode >= rExitCode)
                    return new ChoTokenContent<ChoTokenEnum>(ChoTokenEnum.TRUE, ChoEnum.ToDescription(ChoTokenEnum.TRUE));
                else
                    return new ChoTokenContent<ChoTokenEnum>(ChoTokenEnum.FALSE, ChoEnum.ToDescription(ChoTokenEnum.FALSE));
            }
            else
                throw new InvalidExpressionException("Invalid expression.");
        }

        public void Dispose()
        {
        }
    }
}
